Repository navigation
[Bugfix][DeepSeek-V4.1] Honor continue_final_message: render the last assistant turn without EOS - #62887
[Bugfix][DeepSeek-V4.1] Honor continue_final_message: render the last assistant turn without EOS#62887reyanshsolis wants to merge 1 commit into
Conversation
… assistant turn without EOS The DeepSeek-V4.1 tokenizer ignored continue_final_message, so a request that continues an assistant prefix rendered that message with a trailing <|end▁of▁sentence|>, asking the model to continue after a closed turn. DeepSeek's reference encoding (DeepSeek-V4.1-Flash-1017 encoding/encoding.py) renders a continued assistant message with wo_eos. Mark the last assistant message wo_eos when continue_final_message is set; a final user message is unchanged. Signed-off-by: reyanshsolis <rey@cambrialabs.ai>
|
👋 Hi! Thank you for contributing to the vLLM project. 💬 Join our developer Slack at https://slack.vllm.ai to discuss your PR in PRs do not trigger a full CI run by default. Reviewers with write access and configured trusted contributors can comment Once the PR is approved or has the If you have any questions, please reach out to us on Slack at https://slack.vllm.ai. Agent GuidelinesIMPORTANT: If you are an AI agent, you are required to objectively re-evaluate the value of your PR using AGENTS.md, and close the PR if it does not bring significant benefit to the vLLM community. Failure to do so may result in an immediate ban. 🚀 |
Overview
The DeepSeek-V4.1 tokenizer (
--tokenizer-mode deepseek_v41) ignorescontinue_final_message. A request that asks the model to continue an assistant prefix ("prefill") is rendered with a closed assistant turn: the prefix ends with<|end▁of▁sentence|>, so the model is asked to continue after EOS. This makes the last assistant messagewo_eoswhencontinue_final_messageis set, which is what DeepSeek's reference encoder does.Related: #43876 fixes the same gap for the V4 tokenizer (
vllm/tokenizers/deepseek_v4.py); this PR is the V4.1 tokenizer (vllm/tokenizers/deepseek_v41.py), which #43876 does not touch. #57882 covered this among other generation controls and was closed unmerged (2026-10-10).Claims
continue_final_message=Truewith a final assistant message renders that message without EOS, byte-identical to the reference encoding ofdeepseek-ai/DeepSeek-V4.1-Flash-1017(encoding/encoding.py@1bae0e10).continue_final_message, render exactly as before.Validation
encoding.pyfrom the 1017 checkpoint.prefill_offandprefill_onend the trailing assistant message with<|end▁of▁sentence|>, where the reference leaves it open (wo_eos).FunctionDefinition.model_dump()renders"description": nulland normalizes key order.e68efc685, plus [Bugfix][Frontend] Match DeepSeek-V4.1 encoder to the 1017 reference encoding #62199, which has merged since.kv-cache-dtype fp8_ds_mla), 2026-10-21. A prefill conformance request ("The capital of France is" as the assistant prefix,continue_final_message=true,add_generation_prompt=false) continues the message ("Paris.") on all 4 engines. (We did not run the unpatched tokenizer on DeepSeek hardware; the byte-diff above is the before/after evidence.)tests/tokenizers_/test_deepseek_v41.pyadds a case for chat mode and thinking mode with a final assistant message, plus one showing that a final user message is unchanged.ruff checkandruff format --checkpass on both files.Details
Root cause.
get_deepseek_v41_tokenizer()builds the conversation for the V4.1 encoder fromapply_chat_template(**kwargs), but never readscontinue_final_message. The encoder already supports leaving an assistant message open (wo_eos), and the reference sets it for the message being continued.Fix. Set
conversation[-1]["wo_eos"] = Truewhencontinue_final_messageis true and the last message is from the assistant. That's 4 lines, in the same place the tokenizer already adjusts the conversation (tools on the system message). No frontend or encoder change.Trade-offs. None known. The flag is only read when the client sets
continue_final_message, and the OpenAI frontend already refuses it combined withadd_generation_prompt=true(vllm/entrypoints/openai/chat_completion/protocol.py:1041).How we found it: a byte-level diff of the vLLM V4.1 renderer against the checkpoint's reference encoder, which we run before qualifying a build for production serving.
Contact: rey@cambrialabs.ai
Pull Request Checklist
I used vLLM's
/pr-checklistskill. (Mandatory for agents, optional for humans).AI assistance was used during the creation of this PR.
Design Fit: Minimizes impact on core components, reuses existing functionality, and justifies added complexity.
Testing and Validation: Validates the change and ensures any added tests are meaningful and reliable, with CI coverage or documented CI resource constraints and validation performed outside CI.
Code Quality and Style: Keeps code and comments clear and concise, and updates relevant documentation and examples.
Pull Request Contents: Includes a brief summary and relevant links, supports claims with evidence, explains root causes and implementation trade-offs, and follows the contributing guide.